namespace ImapComponent.Net.Mail.Imap4
{
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Timers;

    using ImapComponent.Net.Mail.Mime;
    using ImapComponent.Net.Mail.Security;
    using ImapComponent.Net.Mail.Utils;

    /// <summary>
    /// A class which provides communication with IMAP server.
    /// </summary>
    public class Imap4Client : TcpClient
    {
        #region Constants

        public const string AllMessages = "1:*";

        #endregion // Constants

        #region Fields

        private static Timer imapTimer = new Timer(60000);
        private readonly Proxy proxy = new Proxy();
        private bool isBusy = false;
        private bool isLoggedIn = false;
        private bool isMailboxSelected = false;
        private MessageFlagSet permanentFlags;
        private MessageFlagSet flags;
        private int messageCount;
        private int recentCount;
        private int unseen;
        private int uidNext;
        private int uidValidity;
        private string[] capabilities;
        private MailboxPermission permission;
        private SslStream sslStream;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Imap4Client class.
        /// </summary>
        public Imap4Client()
        {
            this.Initialize();
        }

        #endregion // Constructors

        #region Properties

        /// <summary>
        /// Gets a value indicating if some commands are in progress or not.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return this.isBusy;
            }
        }

        /// <summary>
        /// Gets a value indicating if the client is connected or not.
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return base.Connected;
            }
        }

        /// <summary>
        /// Gets a value indicating if the client is logged in or not.
        /// </summary>
        public bool IsLoggedIn
        {
            get
            {
                return this.isLoggedIn;
            }
        }

        /// <summary>
        /// Gets a value indicating if the client is in selected state or not.
        /// </summary>
        public bool IsMailboxSelected
        {
            get
            {
                return this.isMailboxSelected;
            }
        }

        /// <summary>
        /// Gets the proxy server information.
        /// </summary>
        public Proxy Proxy
        {
            get
            {
                return this.proxy;
            }
        }

        /// <summary>
        /// Gets the message flags that the client can change
        /// permanently.  If this is missing, the client should
        /// assume that all flags can be changed permanently.
        /// </summary>
        public MessageFlagSet PermanentFlags
        {
            get
            {
                return this.permanentFlags;
            }
        }

        /// <summary>
        /// Gets the defined flags in the mailbox.
        /// </summary>
        public MessageFlagSet Flags
        {
            get
            {
                return this.flags;
            }
        }

        /// <summary>
        /// Gets the number of messages in the mailbox.
        /// </summary>
        public int MessageCount
        {
            get
            {
                return this.messageCount;
            }
        }

        /// <summary>
        /// Gets the number of messages with the \Recent flag set.
        /// </summary>
        public int RecentCount
        {
            get
            {
                return this.recentCount;
            }
        }

        /// <summary>
        /// Gets the message sequence number of the first unseen message in the mailbox.
        /// </summary>
        public int Unseen
        {
            get
            {
                return this.unseen;
            }
        }

        /// <summary>
        /// Gets the next unique identifier value.
        /// </summary>
        public int UidNext
        {
            get
            {
                return this.uidNext;
            }
        }

        /// <summary>
        /// Gets the unique identifier validity value.
        /// </summary>
        public int UidValidity
        {
            get
            {
                return this.uidValidity;
            }
        }

        /// <summary>
        /// Gets the mailbox permission.
        /// </summary>
        public MailboxPermission Permission
        {
            get
            {
                return this.permission;
            }
        }

        /// <summary>
        /// Gets the capabilities of the server.
        /// </summary>
        public string[] Capabilities
        {
            get
            {
                return this.capabilities;
            }
        }

        /// <summary>
        /// Gets or sets the imap timer interval.
        /// </summary>
        public static double Interval
        {
            get
            {
                return imapTimer.Interval / 1000;
            }

            set
            {
                imapTimer.Interval = value * 1000;
            }
        }

        #endregion // Properties

        #region Events

        /// <summary>
        /// Event fired when attempting to connect to the remote server using the specified host.
        /// </summary>
        public event ConnectingEventHandler Connecting;

        /// <summary>
        /// Event fired when the object is connected to the remote server or when the connection failed.
        /// </summary>
        public new event ConnectedEventHandler Connected;

        /// <summary>
        /// Event fired when authentication starts.
        /// </summary>
        public event AuthenticatingEventHandler Authenticating;

        /// <summary>
        /// Event fired when authentication completed.
        /// </summary>
        public event AuthenticatedEventHandler Authenticated;

        /// <summary>
        /// Event fired when data are being read from the server.
        /// </summary>
        public event TcpReadingEventHandler TcpReading;

        /// <summary>
        /// Event fired when data have been read from the server.
        /// </summary>
        public event TcpReadEventHandler TcpRead;

        /// <summary>
        /// Event fired when data are being written to the server.
        /// </summary>
        public event TcpWritingEventHandler TcpWriting;

        /// <summary>
        /// Event fired when data have been written to the server.
        /// </summary>
        public event TcpWrittenEventHandler TcpWritten;

        /// <summary>
        /// Event fired when the imap timer elapsed.
        /// </summary>
        public event ElapsedEventHandler CheckTick;

        /// <summary>
        /// Event fired when NOOP command is issued.
        /// </summary>
        public event NoopingEventHandler Nooping;

        /// <summary>
        /// Event fired when NOOP command completed.
        /// </summary>
        public event NoopedEventHandler Nooped;

        /// <summary>
        /// Event fired when a message status is received.
        /// </summary>
        public event EventHandler MessageStatusReceived;

        /// <summary>
        /// Event fired when attempting to disconnect from the remote server.
        /// </summary>
        public event DisconnectingEventHandler Disconnecting;

        /// <summary>
        /// Event fired when the object disconnected from the remote server.
        /// </summary>
        public event DisconnectedEventHandler Disconnected;

        #endregion // Events

        #region Event Triggers and Logging

        internal void OnConnecting()
        {
            if (Connecting != null)
            {
                Connecting(this);
            }

            Logger.AddEntry("Connecting...", 2);
        }

        internal void OnConnected(ConnectedEventArgs e)
        {
            if (Connected != null)
            {
                Connected(this, e);
            }

            Logger.AddEntry("Connected. Server replied : " + e.ServerResponse + ".", 2);
        }

        internal void OnAuthenticating(AuthenticatingEventArgs e)
        {
            if (Authenticating != null)
            {
                Authenticating(this, e);
            }

            Logger.AddEntry("Authenticating as " + e.Username + "...", 2);
        }

        internal void OnAuthenticated(AuthenticatedEventArgs e)
        {
            if (Authenticated != null)
            {
                Authenticated(this, e);
            }

            Logger.AddEntry("Authenticated as " + e.Username + ".", 2);
        }

        internal void OnTcpReading()
        {
            if (TcpReading != null)
            {
                TcpReading(this);
            }

            Logger.AddEntry("Reading...", 1);
        }

        internal void OnTcpRead(TcpReadEventArgs e)
        {
            if (TcpRead != null)
            {
                TcpRead(this, e);
            }

            foreach (string str in Regex.Split(e.Data, "\r\n"))
            {
                if (str != string.Empty)
                {
                    Logger.AddEntry("Read " + str, 1);
                }
            }
        }

        internal void OnTcpWriting(TcpWritingEventArgs e)
        {
            if (TcpWriting != null)
            {
                TcpWriting(this, e);
            }

            Logger.AddEntry("Sending " + e.Data + "...", 1);
        }

        internal void OnTcpWritten(TcpWrittenEventArgs e)
        {
            if (TcpWritten != null)
            {
                TcpWritten(this, e);
            }

            Logger.AddEntry("Sent " + e.Data + ".", 1);
        }

        internal void OnCheckTick(ElapsedEventArgs e)
        {
            if (CheckTick != null)
            {
                CheckTick(this, e);
            }

            Logger.AddEntry("Checking...", 1);
        }

        internal void OnNooping()
        {
            if (Nooping != null)
            {
                Nooping(this);
            }

            Logger.AddEntry("Nooping...", 1);
        }

        internal void OnNooped()
        {
            if (Nooped != null)
            {
                Nooped(this);
            }

            Logger.AddEntry("Nooped.", 1);
        }

        internal void OnMessageStatusReceived(MessageStatusReceivedEventArgs e)
        {
            if (MessageStatusReceived != null)
            {
                MessageStatusReceived(this, e);
            }

            Logger.AddEntry("Message status received, count or sequence number: " + e.MessageCountOrSequenceNumber, 2);
        }

        internal void OnDisconnecting()
        {
            if (Disconnecting != null)
            {
                Disconnecting(this);
            }

            Logger.AddEntry("Disconnecting...", 2);
        }

        internal void OnDisconnected(DisconnectedEventArgs e)
        {
            if (Disconnected != null)
            {
                Disconnected(this, e);
            }

            Logger.AddEntry("Disconnected.", 2);
        }

        #endregion // Event Triggers and Logging

        #region Methods

        #region Private Helper Methods

        #region Initialization

        /// <summary>
        /// Initializes some imap properties.
        /// </summary>
        private void Initialize()
        {
            imapTimer.Enabled = true;
            imapTimer.Start();
            imapTimer.Elapsed += this.OnTimerElapsed;
        }

        #endregion // Initialization

        #region Connection

        /// <summary>
        /// Does the ssl handshake.
        /// </summary>
        /// <param name="sslHandShake">The ssl handshake.</param>
        private void DoSslHandShake(SslHandShake sslHandShake)
        {
            this.sslStream = new SslStream(base.GetStream(), false, sslHandShake.ServerCertificateValidationCallback, sslHandShake.ClientCertificateValidationCallback);

            bool authenticationFailed = false;

            try
            {
                this.sslStream.AuthenticateAsClient(sslHandShake.TargetHost, sslHandShake.ClientCertificates, sslHandShake.EnabledSslProtocols, sslHandShake.CheckCertificateRevocation);
            }
            catch
            {
                authenticationFailed = true;
            }

            if (authenticationFailed)
            {
                ServicePointManager.CertificatePolicy = new TrustCertificatePolicy();

                this.sslStream.AuthenticateAsClient(sslHandShake.TargetHost, sslHandShake.ClientCertificates, sslHandShake.EnabledSslProtocols, sslHandShake.CheckCertificateRevocation);
            }
        }

        #endregion // Connection

        #region Authentication

        /// <summary>
        /// Authenticates with SASL LOGIN mechanism.
        /// </summary>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        private void AuthenticateLogin(string username, string password)
        {
            this.OnAuthenticating(new AuthenticatingEventArgs(username, password));
            string stamp = this.CreateTag();
            this.Command("AUTHENTICATE LOGIN", stamp);
            this.Command(Convert.ToBase64String(Encoding.ASCII.GetBytes(username)), string.Empty);
            this.Command(Convert.ToBase64String(Encoding.ASCII.GetBytes(password)), string.Empty, stamp);
            this.OnAuthenticated(new AuthenticatedEventArgs(username, password));
        }

        #endregion // Authentication

        #region Stream

        /// <summary>
        /// Gets the communacation stream of this object.
        /// </summary>
        /// <returns>A Stream object, either of type NetworkStream or SslStream if the channel is secured.</returns>
        private new Stream GetStream()
        {
            if (this.sslStream != null)
            {
                return this.sslStream;
            }

            return base.GetStream();
        }

        /// <summary>
        /// Reads one line from the stream.
        /// </summary>
        /// <returns>The line of the stream.</returns>
        private string ReadLine()
        {
            this.OnTcpReading();
            StreamReader streamReader = new StreamReader(this.GetStream(), Encoding.ASCII);
            string response = streamReader.ReadLine();
            this.OnTcpRead(new TcpReadEventArgs(response));
            return response;
        }

        /// <summary>
        /// Creates an unique tag.
        /// </summary>
        /// <returns>A tag.</returns>
        private string CreateTag()
        {
            return DateTime.Now.ToString("yyMMddhhmmss" + DateTime.Now.Millisecond.ToString());
        }

        #endregion // Stream

        #region Select/Examine Mailbox

        /// <summary>
        /// Selects the specified mailbox with the specified permission.
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to select.</param>
        /// <param name="isReadOnly">Select the mailbox with readonly access.</param>
        private void SelectMailbox(string mailboxName, bool isReadOnly)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    string selectCommand = isReadOnly ? "EXAMINE " : "SELECT ";

                    string response = this.Command(selectCommand + "\"" + StringEncoder.EncodeUtf7(mailboxName) + "\"");
                    string[] lines = Regex.Split(response, "\r\n");

                    this.messageCount = Int32.Parse(lines.GetStringContain("EXISTS").Split(' ')[1]);
                    this.recentCount = Int32.Parse(lines.GetStringContain("RECENT").Split(' ')[1]);

                    if (lines.Contains("* OK [UNSEEN "))
                    {
                        this.unseen = Int32.Parse(lines.GetStringContain("* OK [UNSEEN ").Split(' ')[3].TrimEnd(']'));
                    }

                    if (lines.Contains("* OK [UIDNEXT "))
                    {
                        this.uidNext = Int32.Parse(lines.GetStringContain("* OK [UIDNEXT ").Split(' ')[3].TrimEnd(']'));
                    }

                    if (lines.Contains("* OK [UIDVALIDITY "))
                    {
                        this.uidValidity = Int32.Parse(lines.GetStringContain("* OK [UIDVALIDITY ").Split(' ')[3].TrimEnd(']'));
                    }

                    string[] flagArray = lines.GetStringContain("* FLAGS").Substring(8).Trim(new char[] { '(', ')' }).Split(' ');
                    this.flags = MessageFlagSet.CreateMessageFlagSet(flagArray, Helper.GetSystemFlags(flagArray));

                    if (lines.Contains("* OK [PERMANENTFLAGS "))
                    {
                        string flagsString = lines.GetStringContain("* OK [PERMANENTFLAGS ");
                        string[] permanentFlagArray = flagsString.Substring(flagsString.IndexOf('('), flagsString.IndexOf(')') - flagsString.IndexOf('(') + 1).Trim(new char[] { '(', ')' }).Split(' ');
                        this.permanentFlags = MessageFlagSet.CreateMessageFlagSet(permanentFlagArray, Helper.GetSystemFlags(permanentFlagArray));
                    }

                    if (response.ToUpper().Contains("[READ-ONLY]"))
                    {
                        this.permission = MailboxPermission.Read;
                    }
                    else if (response.ToUpper().Contains("[READ-WRITE]"))
                    {
                        this.permission = MailboxPermission.ReadWrite;
                    }

                    this.isMailboxSelected = true;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    this.isMailboxSelected = false;

                    throw new ImapServerNoException("No such mailbox or mailbox can not be accessed.", exception.Message);
                }
            }
        }

        #endregion // Select/Examine Mailbox

        #region Mailbox List

        /// <summary>
        /// Gets mailboxes matching specific criteria.
        /// </summary>
        /// <param name="parentMailboxName">The name of the parent mailbox.</param>
        /// <param name="pattern">The name relative to the parent mailbox name.</param>
        /// <param name="subscribedOnly">Value indicating if get only subscribed mailboxes.</param>
        /// <returns>A mailbox collection.</returns>
        private MailboxCollection GetMailboxes(string parentMailboxName, string pattern, bool subscribedOnly)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    MailboxCollection mailboxCollection = new MailboxCollection();

                    string listCommand = subscribedOnly ? "LSUB " : "LIST ";

                    string response = this.Command(listCommand + "\"" + parentMailboxName + "\" \"" + pattern + "\"");
                    string[] lines = Regex.Split(response, "\r\n");

                    foreach (string line in lines)
                    {
                        if (line.Contains("* " + listCommand))
                        {
                            string delimiterNameString = line.Substring(line.IndexOf(')') + 1).Trim();
                            string delimiter = delimiterNameString.Split(' ')[0].Trim(new char[] { '"' });
                            string rawName = delimiterNameString.Substring(delimiterNameString.IndexOf(' ')).Trim(new char[] { ' ', '"' });
                            string name = StringEncoder.DecodeUtf7(rawName);
                            string shortName = name.LastIndexOf(delimiter) != -1 ? name.Substring(name.LastIndexOf(delimiter) + 1) : name;
                            int nestingLevel = name.GetOccurenceCount(delimiter.ToCharArray()[0]);
                            MailboxFlags flags = this.GetMailboxFlags(line.Substring(line.IndexOf('('), line.IndexOf(')') - line.IndexOf('(') + 1).Split(' '));

                            mailboxCollection.Add(Mailbox.CreateMailbox(name, rawName, shortName, delimiter, flags, nestingLevel));
                        }
                    }

                    return mailboxCollection;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not list the reference or the name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Gets the mailbox flags.
        /// </summary>
        /// <param name="flags">A flags array.</param>
        /// <returns>A mailbox flags.</returns>
        private MailboxFlags GetMailboxFlags(string[] flags)
        {
            MailboxFlags mailboxFlags = MailboxFlags.None;

            foreach (string mailboxFlag in Enum.GetNames(typeof(MailboxFlags)))
            {
                if (flags.Contains("\\" + mailboxFlag))
                {
                    mailboxFlags |= (MailboxFlags)Enum.Parse(typeof(MailboxFlags), mailboxFlag);
                }
            }

            return mailboxFlags;
        }

        #endregion // Mailbox List

        #region Capability

        /// <summary>
        /// Sets the capabilities of the server.
        /// </summary>
        private void SetCapabilities()
        {
            if (this.IsConnected)
            {
                string response = this.Command("CAPABILITY").ToUpper();
                string[] lines = Regex.Split(response, "\r\n");
                string capabilityLine = lines.GetStringContain("CAPABILITY");
                this.capabilities = capabilityLine.Substring(13).Trim().Split(' ');
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        #endregion // Capability

        #region Download

        /// <summary>
        /// Downloads the email messages.
        /// </summary>
        /// <param name="messageIndexSet">An index set of messages to be downloaded.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <returns>A collection of email messages.</returns>
        private EmailMessageCollection Download(string messageIndexSet, bool isUid)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                try
                {
                    this.isBusy = true;

                    string stamp = this.CreateTag();
                    string fetchCommand = isUid ? "UID FETCH" : "FETCH";
                    string command = fetchCommand + " " + messageIndexSet + " " + "(FLAGS INTERNALDATE RFC822.SIZE UID ENVELOPE RFC822)";

                    if (command.Length < 200)
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs("long command data"));
                    }

                    this.GetStream().Write(Encoding.ASCII.GetBytes(stamp + ((stamp.Length > 0) ? " " : "") + command + "\r\n"), 0, stamp.Length + ((stamp.Length > 0) ? 1 : 0) + command.Length + 2);

                    if (command.Length < 200)
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs("long command data"));
                    }

                    this.OnTcpReading();
                    StreamReader streamReader = new StreamReader(this.GetStream(), Encoding.ASCII);
                    string line = string.Empty;
                    string lastline = string.Empty;

                    EmailMessageCollection messageCollection = new EmailMessageCollection();

                    line = streamReader.ReadLine();

                    while (true)
                    {
                        if (line.StartsWith(stamp))
                        {
                            lastline = line;
                            break;
                        }

                        if (line.StartsWith("*"))
                        {
                            int position = line.LastIndexOf("RFC822");
                            string envelopeHead = line.Substring(0, position).Trim();
                            int messageSize = Int32.Parse(line.Substring(position).Substring("RFC822 ".Length).Trim(new char[] { '{', '}' }));
                            char[] charArray = new char[messageSize];
                            streamReader.ReadBlock(charArray, 0, messageSize);
                            string envelopeTail = streamReader.ReadLine();
                            Envelope envelope = Envelope.CreateEnvelope(envelopeHead + envelopeTail);
                            EmailMessage emailMessage = new EmailMessage(envelope, new string(charArray));
                            messageCollection.Add(emailMessage);
                            Logger.AddEntry("Server: Message number " + emailMessage.MessageNumber + " downloaded.");
                            line = streamReader.ReadLine();
                        }
                    }

                    this.isBusy = false;

                    if (lastline.ToUpper().StartsWith(stamp + " OK"))
                    {
                        return messageCollection;
                    }
                    else
                    {
                        if (lastline.ToUpper().StartsWith(stamp + " NO"))
                        {
                            throw new ImapSimpleServerException(lastline.Substring(lastline.IndexOf("NO") + 3));
                        }
                        else
                        {
                            throw new ImapServerBadException("Command unknown or invalid arguments.", lastline.Substring(lastline.IndexOf("BAD") + 4));
                        }
                    }
                }
                finally
                {
                    this.isBusy = false;
                }
            }
        }

        #endregion // Download

        #endregion // Private Helper Methods

        #region Public Methods

        #region Connecting Methods

        #region Connect Methods

        /// <summary>
        /// Connects to the server.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        public void Connect(string host)
        {
            this.Connect(host, 143);
        }

        /// <summary>
        /// Connects to the server.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="port">The server's port.</param>
        public new void Connect(string host, int port)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(host, port);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        /// <summary>
        /// Connects to the specified IP address.
        /// </summary>
        /// <param name="ipAddress">The server's IP address.</param>
        /// <param name="port">The server's port.</param>
        public new void Connect(IPAddress ipAddress, int port)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(ipAddress, port);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        /// <summary>
        /// Connects to the specified IP addresses.
        /// </summary>
        /// <param name="addresses">The IP addresses.</param>
        /// <param name="port">The server's port.</param>
        public new void Connect(IPAddress[] addresses, int port)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(addresses, port);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        public void Connect(string host, string username, string password)
        {
            this.Connect(host, 143, username, password);
        }

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="port">The server's port.</param>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        public void Connect(string host, int port, string username, string password)
        {
            this.Connect(host, port);
            this.Login(username, password);
            this.SetCapabilities();
        }

        #endregion // Connecting Methods

        #region SSL Methods

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        public void ConnectSsl(string host)
        {
            this.ConnectSsl(host, 465, new SslHandShake(host));
        }

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="sslHandShake">The ssl handshake.</param>
        public void ConnectSsl(string host, SslHandShake sslHandShake)
        {
            this.ConnectSsl(host, 465, sslHandShake);
        }

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="port">The server's port.</param>
        public void ConnectSsl(string host, int port)
        {
            this.ConnectSsl(host, port, new SslHandShake(host));
        }

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="host">The server's IP address or domain name.</param>
        /// <param name="port">The server's port.</param>
        /// <param name="sslHandShake">The ssl handshake.</param>
        public void ConnectSsl(string host, int port, SslHandShake sslHandShake)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(host, port);
                this.DoSslHandShake(sslHandShake);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="ipAddress">The server's IP address.</param>
        /// <param name="port">The server's port.</param>
        /// <param name="sslHandShake">The ssl handshake.</param>
        public void ConnectSsl(IPAddress ipAddress, int port, SslHandShake sslHandShake)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(ipAddress, port);
                this.DoSslHandShake(sslHandShake);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        /// <summary>
        /// Connects to the specified host using SslStream.
        /// </summary>
        /// <param name="addresses">The IP addresses.</param>
        /// <param name="port">The server's port.</param>
        /// <param name="sslHandShake">The ssl handshake.</param>
        public void ConnectSsl(IPAddress[] addresses, int port, SslHandShake sslHandShake)
        {
            if (!this.IsConnected)
            {
                this.OnConnecting();
                base.Connect(addresses, port);
                this.DoSslHandShake(sslHandShake);
                string response = this.ReadLine();
                this.OnConnected(new ConnectedEventArgs(response));
                this.SetCapabilities();
            }
            else
            {
                throw new InvalidOperationException("The client is already connected.");
            }
        }

        #endregion // Ssl Methods

        #endregion // Connecting Methods

        #region Command Sending, Receiving

        /// <summary>
        /// Sends the command to the server. The command tag is automatically added.
        /// </summary>
        /// <param name="command">The command (with arguments if necesary) to be sent.</param>
        /// <returns>The server's response.</returns>
        public string Command(string command)
        {
            return this.Command(command, DateTime.Now.ToString("yyMMddhhmmss" + DateTime.Now.Millisecond.ToString()));
        }

        /// <summary>
        /// Sends the command to the server.
        /// </summary>
        /// <param name="command">The command (with arguments if necesary) to be sent.</param>
        /// <param name="stamp">The command tag.</param>
        /// <returns>The server's response.</returns>
        public string Command(string command, string stamp)
        {
            if (this.IsConnected)
            {
                try
                {
                    this.isBusy = true;

                    if (command.Length < 200)
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs("long command data"));
                    }

                    this.GetStream().Write(Encoding.ASCII.GetBytes(stamp + ((stamp.Length > 0) ? " " : "") + command + "\r\n"), 0, stamp.Length + ((stamp.Length > 0) ? 1 : 0) + command.Length + 2);

                    if (command.Length < 200)
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs("long command data"));
                    }

                    this.OnTcpReading();
                    StreamReader streamReader = new StreamReader(this.GetStream(), Encoding.ASCII);
                    StringBuilder buffer = new StringBuilder();
                    string line = string.Empty;
                    string lastline = string.Empty;

                    while (true)
                    {
                        line = streamReader.ReadLine();
                        Logger.AddEntry("Server: " + line);
                        buffer.Append(line + Tokenizer.NewLine);

                        if (line.StartsWith(stamp) || line.StartsWith("+ "))
                        {
                            lastline = line;
                            break;
                        }

                        if (line.EndsWith(MessageStatusState.EXPUNGE.ToString()))
                        {
                            this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.EXPUNGE, Int32.Parse(line.Split(' ')[1]), null));
                        }
                        else if (line.Contains(MessageStatusState.FETCH.ToString()) && !(command.StartsWith("FETCH") || command.StartsWith("UID FETCH")))
                        {
                            MessageFlagSet flags;

                            if (line.Contains("FLAGS ("))
                            {
                                string flagsAndRest = line.Substring(line.IndexOf("FLAGS (") + 6);
                                string[] flagsArray = flagsAndRest.Substring(0, flagsAndRest.IndexOf(")") + 1).Trim(new char[] { '(', ')' }).Split(' ');
                                flags = MessageFlagSet.CreateMessageFlagSet(flagsArray, Helper.GetSystemFlags(flagsArray));
                            }
                            else
                            {
                                flags = null;
                            }

                            this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.FETCH, Int32.Parse(line.Split(' ')[1]), flags));
                        }

                        if (!command.StartsWith("SELECT") && !command.StartsWith("EXAMINE"))
                        {
                            if (line.EndsWith(MessageStatusState.EXISTS.ToString()))
                            {
                                this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.EXISTS, Int32.Parse(line.Split(' ')[1]), null));
                            }
                            else if (line.EndsWith(MessageStatusState.RECENT.ToString()))
                            {
                                this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.RECENT, Int32.Parse(line.Split(' ')[1]), null));
                            }
                        }
                    }

                    if (buffer.Length < 200)
                    {
                        this.OnTcpRead(new TcpReadEventArgs(buffer.ToString()));
                    }
                    else
                    {
                        this.OnTcpRead(new TcpReadEventArgs("long data"));
                    }

                    if (lastline.ToUpper().StartsWith(stamp + " OK") || line.StartsWith("+ "))
                    {
                        return buffer.ToString();
                    }
                    else
                    {
                        if (lastline.ToUpper().StartsWith(stamp + " NO"))
                        {
                            throw new ImapSimpleServerException(lastline.Substring(lastline.IndexOf("NO") + 3));
                        }
                        else
                        {
                            throw new ImapServerBadException("Command unknown or invalid arguments.", lastline.Substring(lastline.IndexOf("BAD") + 4));
                        }
                    }
                }
                finally
                {
                    this.isBusy = false;
                }
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        /// <summary>
        /// Sends the command to the server.
        /// </summary>
        /// <param name="command">The command (with arguments if necesary) to be sent.</param>
        /// <param name="stamp">The command tag.</param>
        /// <param name="checkStamp">The check stamp.</param>
        /// <returns>The server's response.</returns>
        public string Command(string command, string stamp, string checkStamp)
        {
            if (this.IsConnected)
            {
                try
                {
                    this.isBusy = true;

                    if (command.Length < 200)
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWriting(new TcpWritingEventArgs("long command data"));
                    }

                    this.GetStream().Write(Encoding.ASCII.GetBytes(stamp + ((stamp.Length > 0) ? " " : "") + command + "\r\n"), 0, stamp.Length + ((stamp.Length > 0) ? 1 : 0) + command.Length + 2);

                    if (command.Length < 200)
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs(stamp + ((stamp.Length > 0) ? " " : "") + command));
                    }
                    else
                    {
                        this.OnTcpWritten(new TcpWrittenEventArgs("long command data"));
                    }

                    this.OnTcpReading();
                    StreamReader streamReader = new StreamReader(this.GetStream(), Encoding.ASCII);
                    StringBuilder buffer = new StringBuilder();
                    string line = string.Empty;
                    string lastline = string.Empty;

                    while (true)
                    {
                        line = streamReader.ReadLine();
                        Logger.AddEntry("Server: " + line);
                        buffer.Append(line + Tokenizer.NewLine);

                        if (line.StartsWith(checkStamp) || line.StartsWith("+ "))
                        {
                            lastline = line;
                            break;
                        }

                        if (line.EndsWith(MessageStatusState.EXPUNGE.ToString()))
                        {
                            this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.EXPUNGE, Int32.Parse(line.Split(' ')[1]), null));
                        }
                        else if (line.Contains(MessageStatusState.FETCH.ToString()) && !(command.StartsWith("FETCH") || command.StartsWith("UID FETCH")))
                        {
                            MessageFlagSet flags;

                            if (line.Contains("FLAGS ("))
                            {
                                string flagsAndRest = line.Substring(line.IndexOf("FLAGS (") + 6);
                                string[] flagsArray = flagsAndRest.Substring(0, flagsAndRest.IndexOf(")") + 1).Trim(new char[] { '(', ')' }).Split(' ');
                                flags = MessageFlagSet.CreateMessageFlagSet(flagsArray, Helper.GetSystemFlags(flagsArray));
                            }
                            else
                            {
                                flags = null;
                            }

                            this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.FETCH, Int32.Parse(line.Split(' ')[1]), flags));
                        }

                        if (!command.StartsWith("SELECT") && !command.StartsWith("EXAMINE"))
                        {
                            if (line.EndsWith(MessageStatusState.EXISTS.ToString()))
                            {
                                this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.EXISTS, Int32.Parse(line.Split(' ')[1]), null));
                            }
                            else if (line.EndsWith(MessageStatusState.RECENT.ToString()))
                            {
                                this.OnMessageStatusReceived(new MessageStatusReceivedEventArgs(MessageStatusState.RECENT, Int32.Parse(line.Split(' ')[1]), null));
                            }
                        }
                    }

                    if (buffer.Length < 200)
                    {
                        this.OnTcpRead(new TcpReadEventArgs(buffer.ToString()));
                    }
                    else
                    {
                        this.OnTcpRead(new TcpReadEventArgs("long data"));
                    }

                    if (lastline.StartsWith(checkStamp + " OK") || line.StartsWith("+ "))
                    {
                        return buffer.ToString();
                    }
                    else
                    {
                        if (lastline.ToUpper().StartsWith(stamp + " NO"))
                        {
                            throw new ImapSimpleServerException(lastline.Substring(lastline.IndexOf("NO") + 3));
                        }
                        else
                        {
                            throw new ImapServerBadException("Command unknown or invalid arguments.", lastline.Substring(lastline.IndexOf("BAD") + 4));
                        }
                    }
                }
                finally
                {
                    this.isBusy = false;
                }
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        #endregion // Command Sending, Receiving

        #region Implementation of the Imap4 Protocol

        #region Any State Methods

        /// <summary>
        /// Gets a value indicating if the specified capability is supported by the server.
        /// </summary>
        /// <param name="capabilityName">The name of the capability.</param>
        /// <returns>true if server supports the specified capability; otherwise false</returns>
        public bool IsCapabilitySupported(string capabilityName)
        {
            return this.capabilities.Contains(capabilityName.ToUpper());
        }

        /// <summary>
        /// Sends NOOP command to the server. The NOOP command can be used
        /// to reset any inactivity autologout timer on the server.
        /// </summary>
        public void Noop()
        {
            if (this.IsConnected)
            {
                this.OnNooping();
                this.Command("NOOP");
                this.OnNooped();
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        /// <summary>
        /// Logs out and closes the connection with the server.
        /// </summary>
        public void Disconnect()
        {
            if (this.IsConnected)
            {
                this.Command("LOGOUT");
                base.Close();
            }

            this.isLoggedIn = false;
        }

        #endregion // Any State Methods

        #region Not Authenticated State Methods

        /// <summary>
        /// Logs in to the specified account.
        /// </summary>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        public void Login(string username, string password)
        {
            if (this.IsConnected)
            {
                try
                {
                    this.OnAuthenticating(new AuthenticatingEventArgs(username, password));
                    string response = this.Command("LOGIN " + username + " " + password);
                    this.OnAuthenticated(new AuthenticatedEventArgs(username, password));
                    this.isLoggedIn = true;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Invalid credentials.", exception.Message);
                }
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        /// <summary>
        /// Authenticates the specified user with the specified sasl mechanism.
        /// </summary>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        /// <param name="saslMechanism"></param>
        public void Authenticate(string username, string password, SaslMechanism saslMechanism)
        {
            if (this.IsConnected)
            {
                try
                {
                    switch (saslMechanism)
                    {
                        case SaslMechanism.LOGIN:
                            this.AuthenticateLogin(username, password);
                            break;
                    }

                    this.isLoggedIn = true;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Unsupported authentication mechanism or invalid credentials.", exception.Message);
                }
            }
            else
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
        }

        #endregion // Not Authenticated State Methods

        #region Authenticated State Methods

        /// <summary>
        /// Selects a mailbox so that messages in the
        /// mailbox can be accessed.
        /// </summary>
        /// <param name="mailboxName">The name of mailbox to be selected.</param>
        public void SelectMailbox(string mailboxName)
        {
            this.SelectMailbox(mailboxName, false);
        }

        /// <summary>
        /// Selects a mailbox, however, the selected mailbox is identified as read-only.
        /// </summary>
        /// <param name="mailboxName">The name of mailbox to be selected.</param>
        public void ExamineMailbox(string mailboxName)
        {
            this.SelectMailbox(mailboxName, true);
        }

        /// <summary>
        /// Creates a mailbox with the given name.
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to create.</param>
        public void CreateMailbox(string mailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    this.Command("CREATE \"" + StringEncoder.EncodeUtf7(mailboxName) + "\"");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not create mailbox with that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Permanently removes the mailbox with the given name.
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to delete.</param>
        public void DeleteMailbox(string mailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    this.Command("DELETE \"" + StringEncoder.EncodeUtf7(mailboxName) + "\"");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not delete mailbox with that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Changes the name of a mailbox.
        /// </summary>
        /// <param name="mailboxName">The old mailbox name.</param>
        /// <param name="newMailboxName">The new mailbox name.</param>
        public void RenameMailbox(string mailboxName, string newMailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    this.Command("RENAME \"" + StringEncoder.EncodeUtf7(mailboxName) + "\" \"" + StringEncoder.EncodeUtf7(newMailboxName) + "\"");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not rename mailbox with that name or to mailbox with that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Adds the specified mailbox name to the
        /// server's set of "active" or "subscribed" mailboxes
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to subscribe.</param>
        public void SubscribeMailbox(string mailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    this.Command("SUBSCRIBE \"" + StringEncoder.EncodeUtf7(mailboxName) + "\"");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not subscribe to that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Removes the specified mailbox name from
        /// the server's set of "active" or "subscribed" mailboxes
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to unsubscribe.</param>
        public void UnsubscribeMailbox(string mailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    this.Command("UNSUBSCRIBE \"" + StringEncoder.EncodeUtf7(mailboxName) + "\"");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not unsubscribe that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Gets all mailboxes.
        /// </summary>
        /// <returns>A mailbox collection.</returns>
        public MailboxCollection GetMailboxes()
        {
            return this.GetMailboxes(string.Empty, "*", false);
        }

        /// <summary>
        /// Gets mailboxes of the specified parent mailbox matching specific criteria.
        /// </summary>
        /// <param name="parentMailboxName">The name of the parent mailbox.</param>
        /// <param name="pattern">The pattern of the name.</param>
        /// <returns>A mailbox collection of the specified parent mailbox matching the specified pattern.</returns>
        public MailboxCollection GetMailboxes(string parentMailboxName, string pattern)
        {
            return this.GetMailboxes(parentMailboxName, pattern, false);
        }

        /// <summary>
        /// Gets all subscribed mailboxes.
        /// </summary>
        /// <returns>A mailbox collection.</returns>
        public MailboxCollection GetSubscribedMailboxes()
        {
            return this.GetMailboxes(string.Empty, "*", true);
        }

        /// <summary>
        /// Gets subscribed mailboxes of the specified parent mailbox matching specific criteria.
        /// </summary>
        /// <param name="parentMailboxName">The name of the parent mailbox.</param>
        /// <param name="pattern">The pattern of the name.</param>
        /// <returns>A subscribed mailbox collection of the specified parent mailbox matching the specified pattern.</returns>
        public MailboxCollection GetSubscribedMailboxes(string parentMailboxName, string pattern)
        {
            return this.GetMailboxes(parentMailboxName, pattern, true);
        }

        /// <summary>
        /// Gets the delimiter of the mailbox hierarchy.
        /// </summary>
        /// <returns></returns>
        public string GetMailboxDelimiter()
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    string response = this.Command("LIST \"\" \"\"");
                    string[] lines = Regex.Split(response, "\r\n");
                    string delimiterString = lines.GetStringContain("* LIST");

                    return delimiterString.Substring(delimiterString.IndexOf(')') + 1).Trim().Split(' ')[0].Trim(new char[] { '"' });
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not get the hierarchy delimiter.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Gets the status of the specified mailbox.
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox.</param>
        /// <returns>Status information about the specified mailbox.</returns>
        public MailboxStatus GetMailboxStatus(string mailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    string response = this.Command("STATUS \"" + StringEncoder.EncodeUtf7(mailboxName) + "\" " + "(MESSAGES RECENT UNSEEN UIDNEXT UIDVALIDITY)");
                    string statusString = Regex.Split(response, "\r\n").GetStringContain("* STATUS ");
                    string[] attributes = statusString.Substring(statusString.IndexOf('('), statusString.IndexOf(')') - statusString.IndexOf('(') + 1).Trim(new char[] { '(', ')' }).Split(' ');

                    /// note
                    /// string fullMailboxName = mailboxName;
                    string fullMailboxName = StringEncoder.DecodeUtf7(mailboxName);
                    string rawMailboxName = StringEncoder.EncodeUtf7(mailboxName);
                    int messageCount = 0;
                    int recentCount = 0;
                    int unseenCount = 0;
                    int uidNext = 0;
                    int uidValidity = 0;

                    for (int index = 0; index < attributes.Length; index++)
                    {
                        switch (attributes[index])
                        {
                            case "MESSAGES":
                                messageCount = Int32.Parse(attributes[index + 1]);
                                index++;
                                break;
                            case "RECENT":
                                recentCount = Int32.Parse(attributes[index + 1]);
                                index++;
                                break;
                            case "UNSEEN":
                                unseenCount = Int32.Parse(attributes[index + 1]);
                                index++;
                                break;
                            case "UIDNEXT":
                                uidNext = Int32.Parse(attributes[index + 1]);
                                index++;
                                break;
                            case "UIDVALIDITY":
                                uidValidity = Int32.Parse(attributes[index + 1]);
                                index++;
                                break;
                        }
                    }

                    return MailboxStatus.CreateMailboxStatus(fullMailboxName, rawMailboxName, messageCount, recentCount, unseenCount, uidNext, uidValidity);
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("There is no status for that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Appends the specified message at the end of the target mailbox.
        /// </summary>
        /// <param name="message">A message to be append.</param>
        /// <param name="targetMailboxName">A target mailbox name.</param>
        public void UploadMessage(byte[] message, string targetMailboxName)
        {
            this.UploadMessage(message, targetMailboxName, null);
        }

        /// <summary>
        /// Appends the specified message with specified flags at the end of the target mailbox.
        /// </summary>
        /// <param name="message">A message to be append.</param>
        /// <param name="targetMailboxName">A target mailbox name.</param>
        /// <param name="flags">A message flags.</param>
        public void UploadMessage(byte[] message, string targetMailboxName, string[] flags)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else
            {
                try
                {
                    string flagsString = (flags == null) ? "\\Seen" : string.Join(" ", flags);

                    string stamp = this.CreateTag();
                    this.Command("APPEND \"" + StringEncoder.EncodeUtf7(targetMailboxName) + "\" (" + flagsString + ") {" + message.Length + "}", stamp);
                    this.Command(Encoding.ASCII.GetString(message, 0, message.Length), string.Empty, stamp);
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not append to the mailbox or errors in parameters were found.", exception.Message);
                }
            }
        }

        #endregion // Authenticated State Methods

        #region Selected State Methods

        /// <summary>
        /// Requests a checkpoint of the currently selected mailbox.
        /// It is equivalent to Noop method.
        /// </summary>
        public void Check()
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                this.Command("CHECK");
            }
        }

        /// <summary>
        /// Permanently removes all messages that have the
        /// \Deleted flag set from the currently selected mailbox, and returns
        /// to the authenticated state from the selected state. 
        /// </summary>
        public new void Close()
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                this.Command("CLOSE");
                this.isMailboxSelected = false;
            }
        }

        /// <summary>
        /// Permanently removes all messages that have the
        /// \Deleted flag set from the currently selected mailbox.
        /// </summary>
        public void Expunge()
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else if (this.permission == MailboxPermission.Read)
            {
                throw new InvalidOperationException("The mailbox must be selected for read-write mode, use SelectMailbox method instead of ExamineMailbox method.");
            }
            else
            {
                try
                {
                    this.Command("EXPUNGE");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not expunge.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Adds, removes or replaces the specified flags of the specified messages. 
        /// </summary>
        /// <param name="messageIndexSet">A message index set to change flag to.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <param name="flags">Flags to be added, removed or replaced.</param>
        /// <param name="flagAction">A flag action.</param>
        /// <param name="isSilentMode">A value indicating if the silent mode should be used.</param>
        public void ChangeMessageFlags(string messageIndexSet, bool isUid, string[] flags, MessageFlagAction flagAction, bool isSilentMode)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else if (this.permission == MailboxPermission.Read)
            {
                throw new InvalidOperationException("The mailbox must be selected for read-write mode, use SelectMailbox method instead of ExamineMailbox method.");
            }
            else
            {
                try
                {
                    string command = isUid ? "UID STORE" : "STORE";
                    string action = string.Empty;

                    switch (flagAction)
                    {
                        case MessageFlagAction.Add:
                            action = "+FLAGS";
                            break;
                        case MessageFlagAction.Remove:
                            action = "-FLAGS";
                            break;
                        case MessageFlagAction.Replace:
                            action = "FLAGS";
                            break;
                    }

                    string mode = isSilentMode ? ".SILENT" : string.Empty;

                    this.Command(command + " " + messageIndexSet + " " + action + mode + " (" + string.Join(" ", flags) + ")");
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not store that data.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Copies the specified message(s) to the end of the
        /// specified destination mailbox.
        /// </summary>
        /// <param name="messageIndexSet">An index set of messages to copy.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <param name="destinationMailboxName">A destination mailbox name.</param>
        public void CopyMessages(string messageIndexSet, bool isUid, string destinationMailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                try
                {
                    string command = isUid ? "UID COPY" : "COPY";

                    this.Command(command + " " + messageIndexSet + " " + StringEncoder.EncodeUtf7(destinationMailboxName));
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not copy those messages or to that name.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Moves the specified message(s) from selected 
        /// mailbox to the specified mailbox.
        /// </summary>
        /// <param name="messageIndexSet">An index set of messages to move.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <param name="destinationMailboxName">A destination mailbox name.</param>
        public void MoveMessages(string messageIndexSet, bool isUid, string destinationMailboxName)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else if (this.permission == MailboxPermission.Read)
            {
                throw new InvalidOperationException("The mailbox must be selected for read-write mode, use SelectMailbox method instead of ExamineMailbox method.");
            }
            else
            {
                this.CopyMessages(messageIndexSet, isUid, destinationMailboxName);

                try
                {
                    this.ChangeMessageFlags(messageIndexSet, isUid, new string[] { "\\Deleted" }, MessageFlagAction.Add, true);
                }
                catch (ImapServerNoException exception)
                {
                    throw new ImapServerNoException("Can not delete those messages.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Deletes the specified message(s).
        /// </summary>
        /// <param name="messageIndexSet">An index set of messages to mark as deleted.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        public void DeleteMessages(string messageIndexSet, bool isUid)
        {
            try
            {
                this.ChangeMessageFlags(messageIndexSet, isUid, new string[] { "\\Deleted" }, MessageFlagAction.Add, true);
                this.Expunge();
            }
            catch (ImapServerNoException exception)
            {
                throw new ImapServerNoException("Can not delete those messages.", exception.Message);
            }
        }

        /// <summary>
        /// Returns a message uid collection of all messages in selected mailbox.
        /// </summary>
        /// <returns>A message uid collection.</returns>
        public MessageUidCollection Search()
        {
            return this.Search("ALL", true) as MessageUidCollection;
        }

        /// <summary>
        /// Returns a message number collection of messages which satisfying specific criteria.
        /// </summary>
        /// <param name="criteria">Criteria used for search.</param>
        /// <param name="resultIsUid">A value indicating whether the returned collection should be MessageUidCollection or not.</param>
        /// <returns>A message number collection.</returns>
        public MessageNumberCollection Search(string criteria, bool resultIsUid)
        {
            return this.Search(string.Empty, criteria, resultIsUid);
        }

        /// <summary>
        /// Returns a message number collection of messages which satisfying specific criteria.
        /// </summary>
        /// <param name="charset">A charset used for search.</param>
        /// <param name="criteria">Criteria used for search.</param>
        /// <param name="resultIsUid">A value indicating whether the returned collection should be MessageUidCollection or not.</param>
        /// <returns>A message number collection.</returns>
        public MessageNumberCollection Search(string charset, string criteria, bool resultIsUid)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                try
                {
                    MessageNumberCollection resultCollection = resultIsUid ? (MessageNumberCollection)new MessageUidCollection() : (MessageNumberCollection)new MessageSequenceNumberCollection();

                    string command = resultIsUid ? "UID SEARCH" : "SEARCH";
                    string charsetParameter = string.IsNullOrEmpty(charset) ? string.Empty : " CHARSET " + charset;

                    string response = this.Command(command + charsetParameter + " " + criteria);
                    string resultLine = Regex.Split(response, "\r\n").GetStringContain("* SEARCH").Substring(8).Trim();

                    if (!string.IsNullOrEmpty(resultLine))
                    {
                        string[] results = resultLine.Split(' ');

                        foreach (string value in results)
                        {
                            resultCollection.Add(Int32.Parse(value));
                        }
                    }

                    return resultCollection;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not search that charset or that criteria.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Downloads the envelope collection.
        /// </summary>
        /// <param name="messageIndexSet">An index set of envelopes to be downloaded.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <returns>Envelope collection.</returns>
        public EnvelopeCollection DownloadEnvelopes(string messageIndexSet, bool isUid)
        {
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("The client must be connected first.");
            }
            else if (!this.isLoggedIn)
            {
                throw new InvalidOperationException("The client must be authenticated first.");
            }
            else if (!this.isMailboxSelected)
            {
                throw new InvalidOperationException("The mailbox must be selected first.");
            }
            else
            {
                try
                {
                    this.isBusy = true;

                    string command = isUid ? "UID FETCH" : "FETCH";

                    string[] envelopeStrings = Regex.Split(this.Command(command + " " + messageIndexSet + " " + "(FLAGS INTERNALDATE RFC822.SIZE UID ENVELOPE)"), "\r\n");

                    EnvelopeCollection envelopeCollection = new EnvelopeCollection();

                    foreach (string envelopeString in envelopeStrings)
                    {
                        if (envelopeString.StartsWith("*"))
                        {
                            envelopeCollection.Add(Envelope.CreateEnvelope(envelopeString));
                        }
                    }

                    this.isBusy = false;

                    return envelopeCollection;
                }
                catch (ImapServerBadException exception)
                {
                    throw exception;
                }
                catch (ImapSimpleServerException exception)
                {
                    throw new ImapServerNoException("Can not fetch the data.", exception.Message);
                }
            }
        }

        /// <summary>
        /// Downloades the specific email messages.
        /// </summary>
        /// <param name="messageIndexSet">An index set of messages to be downloaded.</param>
        /// <param name="isUid">A value indicating if the message index set is treated as the uid set.</param>
        /// <returns>A collection of email messages.</returns>
        public EmailMessageCollection DownloadMessages(string messageIndexSet, bool isUid)
        {
            try
            {
                return this.Download(messageIndexSet, isUid);
            }
            catch (ImapServerBadException exception)
            {
                throw exception;
            }
            catch (ImapSimpleServerException exception)
            {
                throw new ImapServerNoException("Can not fetch the data.", exception.Message);
            }
        }

        /// <summary>
        /// Downloades the specific email message.
        /// </summary>
        /// <param name="messageIndex">An index of the downloaded message.</param>
        /// <param name="isUid">A value indicating if the message index is treated as the uid number.</param>
        /// <returns>An email messages.</returns>
        public EmailMessage DownloadMessage(int messageIndex, bool isUid)
        {
            try
            {
                return this.Download(messageIndex.ToString(), isUid)[0];
            }
            catch (ImapServerBadException exception)
            {
                throw exception;
            }
            catch (ImapSimpleServerException exception)
            {
                throw new ImapServerNoException("Can not fetch the data.", exception.Message);
            }
        }

        #endregion // Selected State Methods

        #endregion // Implementation of the Imap4 Protocol

        #endregion // Public Methods

        #region Event Methods

        /// <summary>
        /// Fires the CheckTick event.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">Elapsed event arguments.</param>
        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            this.OnCheckTick(e);
        }

        #endregion // Event Methods

        #endregion // Methods
    }
}